package ru.ifmo.kis;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import ru.ifmo.kis.domain.Material;
import ru.ifmo.kis.domaingen.MaterialsEntity;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import java.util.logging.Logger;

public class ImitatingModeling {
	private static final Logger log = Logger.getLogger(HibernateUtil.class.toString());

	private final long seed;
	private final int maxOrders;

	private Random random;

	public ImitatingModeling(long seed, int maxOrders) {
		this.seed = seed;
		this.maxOrders = maxOrders;

		if (seed == 0) {
			random = new Random();
		} else {
			random = new Random(seed);
		}
	}

	private void fillBase() {
		SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
		Session session = null;
		Transaction tx = null;
		try {

			session = sessionFactory.openSession();
			tx = session.beginTransaction();

			MaterialsEntity material = new MaterialsEntity();
			material.setName("Haaaha");
			material.setVolume(100.0f);
//			material.setCount(10);
			log.info("Id=" + session.save(material));
			session.flush();

			Material m = (Material) session.load(Material.class, 1);
			tx.commit();
			session.close();

		} catch (Exception e) {
			tx.rollback();
		} finally {
			session.close();
		}
	}

	private long currentDate;
	private long currentStatusId = 5000; // starting gen-id
	private static final long CUSTOMER_ID = 1;
	private static final long PRODUCT_ID = 1;

	public String randomOrders() {
		StringBuffer result = new StringBuffer();
		result.append("");

		currentDate = System.currentTimeMillis();
		for (int i = 0; i < maxOrders; i++) {
			StringBuffer orderLog = new StringBuffer();

			long orderTimeStamp = nextDate();
			long approveTime = orderTimeStamp;

			orderLog.append(String.format("-- Date:\t%s.\n", new Date(orderTimeStamp).toString()));

//			orderLog.append("INSERT INTO orderstatuses (name) VALUES('new-generated');");
			orderLog.append("INSERT INTO orders (customer, create_date, product_id, `status`, deadline, approved, finished) VALUES (")
					.append(",\n");

			orderLog.append(CUSTOMER_ID).append(",\n");
			orderLog.append(orderTimeStamp).append(",\n");
			orderLog.append(PRODUCT_ID).append(",\n");

			orderLog.append(currentStatusId++).append(",\n");
			orderLog.append(nextDeadLine(orderTimeStamp)).append(",\n");
			orderLog.append(nextApproveTime(orderTimeStamp)).append(",\n");
			orderLog.append(-1).append(",\n");

//			orderLog.append(String.format("Customer\t%d\n", nextCustomer()));
//			orderLog.append(String.format("\t%d\n", nextProduct()));

			orderLog.append(");\n\n");

			result.append(orderLog);
			log.info(orderLog.toString());
		}

		try {
			FileWriter fw = new FileWriter("output.txt");
			fw.append(result);
			fw.close();
		} catch (IOException e) {
			log.severe(e.toString());
		}
		return result.toString();
	}

	private long nextDate() {
		Double rnd;
		rnd = random.nextDouble();
		return currentDate + Math.abs(random.nextLong());
	}

	/**
	 * @param orderTimeStamp
	 * @return 3 days later
	 */
	private long nextFinishTime(long orderTimeStamp) {
		return orderTimeStamp + 1000 * 60 * 24 * 3;
	}

	/**
	 * @param orderTimeStamp
	 * @return 30 min later
	 */
	private long nextApproveTime(long orderTimeStamp) {
		return orderTimeStamp + 1000 * 30;
	}

	/**
	 * @param orderTimeStamp
	 * @return 1 week later
	 */
	private long nextDeadLine(long orderTimeStamp) {
		return orderTimeStamp + 1000 * 60 * 24 * 7;
	}

	private long nextCustomer() {
		return 1;
	}

	private long nextProduct() {
		return 0;
	}

	

	static Random staticRandom = new Random();

	public static void randomDistributionChart(BufferedImage plot, double divider, int maxRandomValue,
											   double param1, double param2) {
		Graphics2D g2 = (Graphics2D) plot.createGraphics();
		g2.setBackground(Color.white);
		g2.clearRect(0, 0, plot.getWidth(), plot.getHeight());

		g2.setColor(Color.black);
		g2.drawRect(0, 0, plot.getWidth() - 1, plot.getHeight() - 1);


		int[] count;
//		count = fillRandomNormal(divider, maxRandomValue);
//		count = fillRandomGaussian(divider, maxRandomValue);
		count = fillRandomGamma(divider, maxRandomValue, param1, param2);

		int maxFrequent = 0;
		for (int i = 0; i < count.length; i++) {
			if (count[maxFrequent] < count[i])
				maxFrequent = i;
		}
		if (count[maxFrequent] == 0) {
			count[maxFrequent] = 1;
			log.severe("No numbers");
		} else {
			log.info("count[maxFrequent] " + count[maxFrequent]);
		}
		double scale;
		if (count[maxFrequent] < 2000)
			scale = (double) plot.getHeight() / 2000;
		else {
			log.severe("count[maxFrequent]=" + count[maxFrequent]);
			scale = (double) plot.getHeight() / count[maxFrequent];
		}

		g2.setColor(new Color(0x000077));

		int barWidth = 1;
		int margin = 2;
		for (int i = 0; i < count.length; i++) {
			int val = count[i];
			final int x = i * (barWidth + margin);
			final int chartHeight = plot.getHeight();
			final int barHeight = (int) (val * scale);
			g2.drawRect(x, chartHeight - barHeight, barWidth, chartHeight);

			if (x >= plot.getWidth()) {
				log.severe("Go further than image. #" + i);
				break;
			}
		}
	}

	private static int[] fillRandomPlain(double divider, int maxRandomValue) {
		int[] count = new int[maxRandomValue];

		for (long i = 0; i < 10000; i++) {
			int randomValue;
			randomValue = staticRandom.nextInt(100);
			count[randomValue]++;
		}
		return count;
	}

	private static int[] fillRandomGaussian(double divider, int maxRandomValue) {
		int[] count = new int[maxRandomValue];

		for (long i = 0; i < 10000; i++) {
			double randomDouble;
			int randomValue;

			randomDouble = (staticRandom.nextGaussian() / divider + .5); // interested in (0,1)
			randomValue = (int) (Math.abs(randomDouble * maxRandomValue) % maxRandomValue);

//			System.out.println("randomDouble ==" + randomDouble);
//			System.out.println("randomValue =" + randomValue);
			count[randomValue]++;
		}
		return count;
	}

	private static int[] fillRandomNormal(double divider, int maxRandomValue) {
		int[] count = new int[maxRandomValue];

		for (long i = 0; i < 10000; i++) {
			double randomDouble;
			int randomValue;
			double nextRandom = normal(1., 1.);

			randomDouble = (nextRandom / divider + .5); // interested in (0,1)
			randomValue = (int) (Math.abs(randomDouble * maxRandomValue) % maxRandomValue);

//			System.out.println("randomDouble ==" + randomDouble);
//			System.out.println("randomValue =" + randomValue);
			count[randomValue]++;
		}
		return count;
	}

	private static int[] fillRandomGamma(double divider, int maxRandomValue, double param1, double param2) {
		int[] count = new int[maxRandomValue];

		for (long i = 0; i < 10000; i++) {
			double shiftedRandom;
			int randomInt;
			double nextRandom = randomGamma(param1, param2);

//			shiftedRandom = (nextRandom / divider + .5); // interested in (0,1)
			shiftedRandom = nextRandom / divider; // interested in (0,1)
			randomInt = (int) (Math.abs(shiftedRandom * maxRandomValue) % maxRandomValue);

			System.out.println("shiftedRandom ==" + shiftedRandom);
//			System.out.println("randomInt =" + randomInt);
			count[randomInt]++;
		}
		return count;
	}

	/**
	 * Generate random number with normal distribution.
	 * stocc C lib.
	 *
	 * @param m
	 * @param s
	 * @return random number
	 */
	static double normal(double m, double s) {
		// normal distribution with mean m and standard deviation s
		double normal_x1, normal_x2;				   // first random coordinate (normal_x2 is member of class)
		double w;						   // radius
		// make two normally distributed variates by Box-Muller transformation
		do {
			normal_x1 = 2. * staticRandom.nextDouble() - 1.;
			normal_x2 = 2. * staticRandom.nextDouble() - 1.;
			w = normal_x1 * normal_x1 + normal_x2 * normal_x2;
		}
		while (w >= 1. || w < 1E-30);
		w = Math.sqrt(Math.log(w) * (-2. / w));
		normal_x1 *= w;
		normal_x2 *= w;	// normal_x1 and normal_x2 are independent normally distributed variates
		return normal_x1 * s + m;
	}

	/**
	 * Generate random number with Gamma distribution.
	 *
	 * @param k seems delta
	 * @param a seems k
	 * @return random number
	 */
	static double randomGamma(double k, double a) {
		double tr = 1;
		for (int i = 1; i < Math.round(k); i++) {
			tr *= staticRandom.nextDouble();
		}
		return -Math.log(tr) / a;
	}

}
